/*
 * This file is part of animus, a GUI for the command line app "GHost++"
 *
 * http://code.google.com/p/animus/
 *
 * Copyright (C) 2008-2009 Lucas Romero
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
 */


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Mono.Nat;

namespace animus
{
    public partial class FormMain : Form
    {
        private enum EGHostStatus
        {
            Active,
            Inactive,
        }
        private enum EGHostType
        {
            Local,
            Remote
        }
        private enum EGameStatus
        {
            None,
            Lobby
        }
        EGHostType GHostType = EGHostType.Local;
        EGHostStatus GHostStatus = EGHostStatus.Inactive;
        EGameStatus GameStatus = EGameStatus.None;
        private bool UPnPBroadcastEnabled = false;

        List<INatDevice> m_NatDevices = new List<INatDevice>();

        [DllImport("user32.dll")]
        static extern IntPtr PostMessage(
            IntPtr wnd,
            uint Msg,
            IntPtr wParam,
            IntPtr lParam);
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        const uint WM_VSCROLL = 0x0115;
        const uint SB_BOTTOM = 7;
        private ASCIIEncoding m_NetEncode = new ASCIIEncoding();

        ConsoleTriggerCollection m_Triggers = new ConsoleTriggerCollection();
        private delegate void GHostStarter();
        private UdpClient m_CommandSocket = new UdpClient(Properties.Settings.Default.UDPCommandReceiver, Properties.Settings.Default.UDPCommandPort);

        private class StateObject
        {
            public UdpClient workSocket = null;
            public const int BUFFER_SIZE = 1024;
            public byte[] buffer = new byte[BUFFER_SIZE];
            public StringBuilder sb = new StringBuilder();
        }

        public static bool IsRunningOnMono()
        {
            return Type.GetType("Mono.Runtime") != null;
        }

        private void UpdateTitle(string ghostversion)
        {
            Text = String.Format("animus GHost++ GUI Version {0}", Application.ProductVersion);
            if (ghostversion != string.Empty)
                Text += String.Format(" - GHost++ Version " + ghostversion);
        }

        private void Read_Callback(IAsyncResult ar)
        {
            StateObject so = (StateObject)ar.AsyncState;
            UdpClient s = so.workSocket;
            IPEndPoint sender = new IPEndPoint(IPAddress.Any, Properties.Settings.Default.UDPCommandPort);
            byte[] data = null;
            string recvData = String.Empty;
            try
            {
                data = s.EndReceive(ar, ref sender);
            }
            catch (System.Exception ex)
            {
                printError("Could not receive from RCON socket: " + ex.Message);
                
            }
            finally
            {
                m_CommandSocket.BeginReceive(new AsyncCallback(Read_Callback), ar.AsyncState);
            }
            if (data != null)
                recvData = ASCIIEncoding.ASCII.GetString(data, 0, data.Length);
            if (recvData == "PING")
                s.Send(new byte[] { (byte)'\n' }, 1);
            else
                printLine(recvData);
        }

        public FormMain()
        {
            if (Properties.Settings.Default.NewVersionInstalled)
            {
                Properties.Settings.Default.Upgrade();
                Properties.Settings.Default.NewVersionInstalled = false;
            }
            if (Properties.Settings.Default.LanGamePunchPort == 0)
            {
                // assign random port
                // between 49152 and 65535
                Random r = new Random();
                Properties.Settings.Default.LanGamePunchPort = r.Next(49152, 65535);
            }
            if (Properties.Settings.Default.UPnPBroadcastPort == 0)
            {
                // assign random port
                // between 49152 and 65535
                Random r = new Random();
                Properties.Settings.Default.UPnPBroadcastPort = r.Next(49152, 65535);
            }

            InitializeComponent();

            NatUtility.DeviceFound += new EventHandler<DeviceEventArgs>(NatUtility_DeviceFound);
            if (Properties.Settings.Default.UPnPEnabled)
                NatUtility.StartDiscovery();
            

            ghostProcess.Dispose();
            ghostProcess = null;
            UpdateTitle(string.Empty);
            statusStrip1.Items.Insert(1, new ToolStripSeparator());
            try
            {
                using (StreamReader autoCompletionStream = new StreamReader("commands.txt"))
                {
                    tbCommandInput.AutoCompleteCustomSource.Clear();
                    while (!autoCompletionStream.EndOfStream)
                    {
                        tbCommandInput.AutoCompleteCustomSource.Add(autoCompletionStream.ReadLine().Trim());
                    }
                }
            }
            catch (System.Exception ex)
            {
                printError("Could not load commands from 'commands.txt': " + ex.Message + " (using defaults)");
            }
            
            try
            {
                m_Triggers.Load(Properties.Settings.Default.TriggerFile);
            }
            catch (System.Exception e)
            {
                string toAppend = string.Empty;
                if (e.InnerException != null)
                    toAppend = " (" + e.InnerException.Message + ")";
            	printError("Could not load trigger file: " + e.Message +  toAppend);
            }
            
            ConsoleTriggerFontChange.SetTextContainer(ghostOutputText);
            ConsoleTriggerTrayBalloon.SetNotifyIcon(trayIcon);

            //Create Handle because we want to use Invoke() before showing the form which would normally create the handle
            CreateHandle();
            CreateControl();
            
            //clone menu items
            fileToolStripMenuItem.DropDownItems.Add(new ToolStripMenuItem(showHideWindowToolStripMenuItem.Text, showHideWindowToolStripMenuItem.Image, showHideWindowToolStripMenuItem_Click));
            fileToolStripMenuItem.DropDownItems.Add(new ToolStripMenuItem(exitGHostToolStripMenuItem.Text, exitGHostToolStripMenuItem.Image, exitGHostToolStripMenuItem_Click));

            Connect(Properties.Settings.Default.UDPCommandReceiver, Properties.Settings.Default.UDPCommandPort);
            //ghostOutputText.Rtf = @"{\rtf1\ansi\deff0{\fonttbl{\f0 " + ghostOutputText.Font.Name + @";}}}";
            tbCommandInput.Select();
        }

        private void ghostProcess_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            printLine(e.Data);
        }

        private void Connect(string host, int port)
        {
            GHostStatus = EGHostStatus.Inactive;
            bool local = false;
            IPAddress[] addresses = Dns.GetHostAddresses(host);
            foreach (IPAddress addr in addresses)
            {
                if( IPAddress.IsLoopback(addr) )
                    local = true;
            }
            advancedToolStripMenuItem.Visible = false;
            if (local && !IsGHostListeningLocally(port))
            {
                GHostType = EGHostType.Local;
                startGHost();
                tslBroadcast.Visible = false;
                punchHoleInFirewallManuallyToolStripMenuItem.Visible = false;
            }
            else
            {
                GHostType = EGHostType.Remote;
                punchHoleInFirewallManuallyToolStripMenuItem.Visible = true;
                advancedToolStripMenuItem.Visible = true;
                tslBroadcast.Text = "AutoBroadcast disabled";
                tslBroadcast.Image = Properties.Resources.broadcastoff16;
                tslBroadcast.Visible = true;
                StateObject state = new StateObject();
                state.workSocket = m_CommandSocket;
                m_CommandSocket.BeginReceive(new AsyncCallback(Read_Callback), state);
                string loginCmd = "rcon_password " + Properties.Settings.Default.RconPassword;
                m_CommandSocket.Send(Encoding.ASCII.GetBytes(loginCmd), Encoding.ASCII.GetByteCount(loginCmd));
            }
            
            /*foreach (ToolStripItem i in advancedToolStripMenuItem.DropDownItems)
            {
                if (i.Visible)
                {
                    advancedToolStripMenuItem.Visible = true;
                    break;
                }
            }*/
        }

        void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
        {
            printGUILine("NAT Device discovered: " + e.Device.ToString());
            m_NatDevices.Add(e.Device);
            UPnPTimer.Start();
        }

        

        private void process1_Exited( object sender, EventArgs e )
        {
            //print exit message
            printLine("GHost++ exited");
            GHostStatus = EGHostStatus.Inactive;
            //update statusbar
            statusGHostProcess.Text = "Status: not running";
            statusGHostProcess.Image = Properties.Resources.ledred16;
        }

        private void printText(string text, bool trigger, bool newline)
        {
            if (ghostOutputText.InvokeRequired)
            {
                ghostOutputText.Invoke(new printDelegate(printText), new object[] { text, trigger, newline });
                return;
            }
            if (string.IsNullOrEmpty(text) || text.Trim() == string.Empty)
                return;
            bool suppressOutput = false;
            if (trigger)
            {
                foreach (ConsoleTrigger t in m_Triggers)
                {
                    try
                    {
                        t.Evaluate(text);
                        suppressOutput |= t.SuppressOutput;
                    }
                    catch (System.Exception ex)
                    {
                        printLine("[TRIGGER] ERROR while processing trigger #" + Convert.ToString(m_Triggers.IndexOf(t) + 1) + ": " + ex.Message, false);
                    }
                }
            }

            Match ghostversion = Regex.Match(text, @"\[GHOST\] GHost\+\+ Version (.*)");
            if (ghostversion.Success)
            {
                UpdateTitle(ghostversion.Groups[1].Value);
                StateObject state = new StateObject();
                state.workSocket = m_CommandSocket;
                m_CommandSocket.BeginReceive(new AsyncCallback(Read_Callback), state);
                string loginCmd = "rcon_password " + Properties.Settings.Default.RconPassword;
                m_CommandSocket.Send(Encoding.ASCII.GetBytes(loginCmd), Encoding.ASCII.GetByteCount(loginCmd));
            }

            if (text == @"[RCON] You are now authed")
            {
                statusGHostProcess.Text = "Status: Connected";
                statusGHostProcess.Image = Properties.Resources.ledgreen16;
                GHostStatus = EGHostStatus.Active;
                if (Properties.Settings.Default.EnableAutoBroadcast && GHostType == EGHostType.Remote)
                    EnableAutoBroadcast();
                if (GHostType == EGHostType.Local)
                {
                    try
                    {
                    	ghostProcess.CancelOutputRead();
                    }
                    catch (System.Exception e)
                    {
                    	// only catch
                    }
                }
            }

            if (text == @"[RCON] Your session timed out")
            {
                statusGHostProcess.Text = "Status: Session Timeout";
                statusGHostProcess.Image = Properties.Resources.ledred16;
                GHostStatus = EGHostStatus.Inactive;
            }

            Match ghostExit = Regex.Match(text, @"\[GHOST\] shutting down");
            if (ghostExit.Success)
            {
                statusGHostProcess.Text = "Status: Disconnected";
                statusGHostProcess.Image = Properties.Resources.ledred16;
                GHostStatus = EGHostStatus.Inactive;
            }

            Match ghostBroadcast = Regex.Match(text, @"\[RCON\] Enabled autobroadcasting from port (\d+) to (.*?):(\d+)");
            if (ghostBroadcast.Success)
            {
                tslBroadcast.Text = "AutoBroadcast enabled";
                tslBroadcast.Image = Properties.Resources.broadcast16;
            }

            Match gameDetails = Regex.Match(text, @"\[RCON\] Game details: (.*)", RegexOptions.Singleline);
            if (gameDetails.Success)
            {
                suppressOutput = true;
                EventGameDetails(gameDetails.Groups[1].Value);
            }

            Match gameCreated = Regex.Match(text, @"\[GAME: (.*?)\] listening on port (\d+)");
            if (gameCreated.Success && gameCreated.Groups[1].Value != "GHost++ Admin Game")
            {
                try
                {
                	EventGameHosted(gameCreated.Groups[1].Value, Int32.Parse(gameCreated.Groups[2].Value));
                }
                catch (System.Exception ex)
                {
                    printError("Could not parse creation information: " + ex.Message);
                }
            }

            Match gameAlreadyInLobby = Regex.Match(text, @"\[LOCAL: (.*?)\] /w (.*?) Unable to create game \[(.*?)\]\. Another game \[(.*?) : (.*?) : (.*?) : (.*?)\] is in the lobby\.");
            if (gameAlreadyInLobby.Success)
                EventGameHosted(gameAlreadyInLobby.Groups[4].Value, -1);
            /*[LOCAL: europe.battle.net] /w alarm_im_darm Unable to create game [rolff0r]. Another game [rolff0r : alarm_im_darm : 0/10 : 4m] is in the lobby.*/

            Match gameDeleted = Regex.Match(text, @"\[GHOST\] deleting current game \[(.*?)\]");
            if (gameDeleted.Success)
            {
                EventGameUnhosted(gameDeleted.Groups[1].Value);
            }
            Match lobbyChat = Regex.Match(text, @"\[GAME: (.*?)\] \[Lobby\] \[(.*?)\]: (.*)");
            if (lobbyChat.Success && lobbyChat.Groups[1].Value != "GHost++ Admin Game")
            {
                rtbCurrentChatOutput.AppendText(String.Format("{0}: {1}\n", lobbyChat.Groups[2].Value.PadRight(15), lobbyChat.Groups[3].Value));
                tabCurrentGame.Text = lobbyChat.Groups[1].Value;
            }

            #region progressbar for ip2country loading status
            try
            {
	            Match ip2country = Regex.Match(text, @"\[GHOST\] iptocountry data: (\d+)% loaded");
	            if (ip2country.Success)
	            {
	                int progress = int.Parse(ip2country.Groups[1].Value);
	                if (progress == 10)
	                {
	                    tsProgress.Visible = true;
	                    tsStatusText.Text = "Loading IP2Country data:";
	                }
	                else if (progress == 100)
	                {
	                    tsProgress.Visible = false;
	                    tsStatusText.Text = string.Empty;
	                }
	                tsProgress.Value = progress;
	            }
            }
            catch
            {
                /* this error is non-critical */
                tsProgress.Visible = false;
                tsStatusText.Text = string.Empty;
            }
            #endregion

            #region track bnet connection state
            if (Properties.Settings.Default.ShowBnetConnectionStatus)
            {
                try
                {
                    Match bnetfound = Regex.Match(text, @"\[GHOST\] found battle\.net connection #1 for server \[(.*?)\]");
                    if (bnetfound.Success)
                    {
                        bool foundbnet = false;
                        foreach (ToolStripItem i in statusStrip1.Items)
                        {
                            if (i.Text == bnetfound.Groups[1].Value)
                            {
                                foundbnet = true;
                                break;
                            }
                        }
                        if (!foundbnet)
                        {
                            ToolStripStatusLabel label = new ToolStripStatusLabel(bnetfound.Groups[1].Value, Properties.Resources.ledred16);
                            label.Name = "bnetconn";
                            label.TextImageRelation = TextImageRelation.TextBeforeImage;
                            statusStrip1.Items.Insert(2, label);
                        }
                    }
                }
                catch
                {
                    /* this error is non-critical */
                }

                try
                {
                    Match bnetconnected = Regex.Match(text, @"\[BNET: (.*?)\] connected");
                    if (bnetconnected.Success)
                    {
                        foreach (ToolStripItem i in statusStrip1.Items)
                        {
                            if (i.Text == bnetconnected.Groups[1].Value)
                            {
                                i.Image = Properties.Resources.ledgreen16;
                            }
                        }
                    }
                }
                catch
                {
                    /* this error is non-critical */
                }

                try
                {
                    Match bnetdisconnected = Regex.Match(text, @"\[BNET: (.*?)\] disconnected");
                    if (bnetdisconnected.Success)
                    {
                        foreach (ToolStripItem i in statusStrip1.Items)
                        {
                            if (i.Text == bnetdisconnected.Groups[1].Value)
                            {
                                i.Image = Properties.Resources.ledred16;
                            }
                        }
                    }
                }
                catch
                {
                    /* this error is non-critical */
                }
            }
            #endregion
            if (!suppressOutput)
            {
                if (newline)
                    ghostOutputText.AppendText(Environment.NewLine);
                ghostOutputText.AppendText(text);
            }
        }

        private void UpdatePlayer( int index, string name, string ping, string from, string flags, int team)
        {
            ListViewItem i;
            if (index < lwCurrentPlayers.Items.Count)
                i = lwCurrentPlayers.Items[index];
            else
            {
                i = new ListViewItem();
                i.UseItemStyleForSubItems = false;
                i.SubItems.Add("");
                i.SubItems.Add("");
                i.SubItems.Add("");
                i.SubItems.Add("");
                Color backColor = lwCurrentPlayers.BackColor;
                if (lwCurrentPlayers.Groups.Count <= team)
                    lwCurrentPlayers.Groups.Add("lwgTeam" + Convert.ToString(team + 1), "Team " + Convert.ToString(team + 1));
                i.Group = lwCurrentPlayers.Groups[team];
                switch (index)
                {
                    case 0:
                        backColor = Color.FromArgb(0, 66, 255);
                        break;
                    case 1:
                        backColor = Color.FromArgb(28, 230, 185);
                        break;
                    case 2:
                        backColor = Color.FromArgb(84, 0, 129);
                        break;
                    case 3:
                        backColor = Color.FromArgb(255, 252, 1);
                        break;
                    case 4:
                        backColor = Color.FromArgb(254, 138, 14);
                        break;
                    case 5:
                        backColor = Color.FromArgb(229, 91, 176);
                        break;
                    case 6:
                        backColor = Color.FromArgb(149, 150, 151);
                        break;
                    case 7:
                        backColor = Color.FromArgb(126, 191, 241);
                        break;
                    case 8:
                        backColor = Color.FromArgb(16, 98, 70);
                        break;
                    case 9:
                        backColor = Color.FromArgb(78, 42, 4);
                        break;
                }
                i.SubItems[4].BackColor = backColor;
                lwCurrentPlayers.Items.Add(i);
            }
            i.Text = name;
            i.SubItems[1].Text = ping;
            i.SubItems[2].Text = from;
            i.SubItems[3].Text = flags;
            if (flags.Contains("R"))
                i.ImageKey = "userrootadmin16.png";
            else if (flags.Contains("A"))
                i.ImageKey = "useradmin16.png";
            else if (flags.Contains("r"))
                i.ImageKey = "userreserved16.png";
            else if (flags.Contains("o"))
                i.ImageKey = "userinactive16.png";
            else if (flags.Contains("c"))
                i.ImageKey = "lock16.png";
            else
                i.ImageKey = "user16.png";
        }

        private delegate void printDelegate(string text, bool trigger, bool newline);
        private delegate void printLineDelegate(string text, bool trigger);
        private void printLine(string text, bool trigger)
        {
            if (string.IsNullOrEmpty(text) || text.Trim() == string.Empty)
                return;
            if (ghostOutputText.InvokeRequired)
            {
                ghostOutputText.Invoke(new printLineDelegate(printLine), new object[] { text, trigger });
                return;
            }
            printText(text, trigger, true);
        }

        private void printLine(string text)
        {
            printLine(text, true);
        }

        private void printGUILine(string text)
        {
            printLine("[GUI] " + text, true);
        }

        #region Form loading/exiting

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
	            if (e.CloseReason == CloseReason.UserClosing && ghostProcess != null && !ghostProcess.HasExited)
	            {
	                e.Cancel = true;
	                Hide();
	            }
	            else
	                stopGHost();
            }
            catch (System.InvalidOperationException ex)
            {
            	
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            stopGHost();
            trayIcon.Dispose();
            Exit();
        }
        #endregion

        private void trayIcon_MouseDoubleClick( object sender, MouseEventArgs e )
        {
            showHideWindowToolStripMenuItem_Click(sender, e);
        }

        private void exitGHostToolStripMenuItem_Click( object sender, EventArgs e )
        {
            bool isRunning = false;
            if (ghostProcess != null)
            {
                try
                {
                    isRunning = !ghostProcess.HasExited;
                }
                catch (InvalidOperationException)
                {
            	    //just catch
                }
            }
            if ( !isRunning || MessageBox.Show("Are you sure you want to exit?", "Are you sure?", MessageBoxButtons.YesNo) == DialogResult.Yes)
                Exit();
        }

        private void Exit()
        {
            try
            {
                m_Triggers.Save(Properties.Settings.Default.TriggerFile);
            }
            catch (System.Exception e)
            {
                printError("Could not save trigger file: " + e.Message);
            }
            Properties.Settings.Default.Save();
            Application.Exit();
        }

        private void printError(string error, bool trigger)
        {
            printLine("[GUI] ERROR: " + error, trigger);
        }

        private void printError(string error)
        {
            printError(error, true);
        }

        private void enableInput()
        {
            tbCommandInput.Enabled = true;
            tbCommandInput.Text = "";
        }

        private void disableInput()
        {
            tbCommandInput.Enabled = false;
            tbCommandInput.Text = "GHost++ is not listening for commands on the port configured in the settings dialog";
        }

        #region Starting & stopping ghost
        private void startGHost( )
        {
            //Invoke startGHost to avoid threading issues with unsafe controls
            if (InvokeRequired)
            {
                Invoke(new GHostStarter(startGHost));
                return;
            }

            try
            {
                ghostProcess = new Process();
                ghostProcess.EnableRaisingEvents = true;
                ghostProcess.StartInfo.CreateNoWindow = true;
                ghostProcess.StartInfo.Domain = "";
                ghostProcess.StartInfo.ErrorDialog = true;
                ghostProcess.StartInfo.FileName = Properties.Settings.Default.GhostExecutable;
                ghostProcess.StartInfo.LoadUserProfile = false;
                ghostProcess.StartInfo.Password = null;
                ghostProcess.StartInfo.RedirectStandardError = true;
                ghostProcess.StartInfo.RedirectStandardOutput = true;
                ghostProcess.StartInfo.StandardErrorEncoding = null;
                ghostProcess.StartInfo.StandardOutputEncoding = null;
                ghostProcess.StartInfo.UserName = "";
                ghostProcess.StartInfo.UseShellExecute = false;
                ghostProcess.StartInfo.WorkingDirectory = System.IO.Directory.GetCurrentDirectory();
                ghostProcess.SynchronizingObject = this;
                ghostProcess.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(this.ghostProcess_OutputDataReceived);
                ghostProcess.Exited += new System.EventHandler(this.process1_Exited);
                ghostProcess.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(this.ghostProcess_OutputDataReceived);
                GHostStatus = EGHostStatus.Active;
                //Start GHost process
                ghostProcess.Start();
                //update statusbar
                
                statusGHostProcess.Text = "Status: running";
                statusGHostProcess.Image = Properties.Resources.ledgreen16;
                try
                {
                    //set process priority as specified in preferences,
                    //catch eventual exceptions (e.g. no permissions)
                    ghostProcess.PriorityClass = Properties.Settings.Default.GHostProcessPriority;
                }
                catch (System.Exception e)
                {
                    //print error
                    printError("Could not set process priority: " + e.Message);
                }
                try
                {
                    //start listening for GHost's output (stdout + stderr)
                    ghostProcess.BeginOutputReadLine();
                    ghostProcess.BeginErrorReadLine();
                }
                catch (System.Exception ex)
                {
                    printError("Error attaching to process: " + ex.Message);
                }
                //timerCheckPort.Start();
            }
            catch (System.Exception ex)
            {
                printError("Error starting GHost: " + ex.Message);
            }
        }

        private void stopGHost( )
        {
            GHostStatus = EGHostStatus.Inactive;
            try
            {
	            ghostProcess.CancelOutputRead();
	            ghostProcess.CancelErrorRead();
            }
            catch
            {
            	//just catch
            }
            try
            { 
                if ( !ghostProcess.CloseMainWindow( ) )
                    ghostProcess.Kill( );
                if (!ghostProcess.HasExited)
                    ghostProcess.WaitForExit(1000);
            }
            catch (Exception ex)
            { printError( "Error while terminating ghost: " + ex.Message ); }

            // set all bnet status indicators to offline
            foreach (ToolStripItem i in statusStrip1.Items)
            {
                if (i.Name == "bnetconn")
                {
                    i.Image = Properties.Resources.ledred16;
                }
            }
        }

        #region start/stop/restart context menu
        private void ghostStatusStrip_Opening(object sender, CancelEventArgs e)
        {
            if (GHostType == EGHostType.Local && GHostStatus == EGHostStatus.Active && ghostProcess != null)
            {
                restartToolStripMenuItem.Text = "Restart";
                startToolStripMenuItem.Text = "Start";
                stopToolStripMenuItem.Text = "Exit";
                
                try
                {
	                startToolStripMenuItem.Visible = ghostProcess.HasExited;
	                stopToolStripMenuItem.Visible = !ghostProcess.HasExited;
	                restartToolStripMenuItem.Visible = !ghostProcess.HasExited;
                }
                catch (System.InvalidOperationException)
                {
                    startToolStripMenuItem.Visible = true;
                    stopToolStripMenuItem.Visible = false;
                    restartToolStripMenuItem.Visible = false;
                }
            }
            else if( GHostType == EGHostType.Remote )
            {
                restartToolStripMenuItem.Text = "Reconnect";
                startToolStripMenuItem.Text = "Connect";
                stopToolStripMenuItem.Text = "Kill GHost";
                startToolStripMenuItem.Visible = (GHostStatus == EGHostStatus.Inactive);
                restartToolStripMenuItem.Visible = (GHostStatus == EGHostStatus.Active);
                stopToolStripMenuItem.Visible = (GHostStatus == EGHostStatus.Active) && Properties.Settings.Default.EnableRemoteGHostKilling;
            }
            else
                e.Cancel = true;
        }

        private void startToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GHostType == EGHostType.Local)
                startGHost();
            else
                Connect(Properties.Settings.Default.UDPCommandReceiver, Properties.Settings.Default.UDPCommandPort);
        }


        private void stopToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if( GHostType == EGHostType.Local )
                stopGHost();
            else if (GHostStatus == EGHostStatus.Active)
            {
                ExecuteCommand("exit", false);
            }
                
        }

        private void restartToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GHostType == EGHostType.Local)
            {
                stopGHost();
                startGHost();
            }
            else
                Connect(Properties.Settings.Default.UDPCommandReceiver, Properties.Settings.Default.UDPCommandPort);
        }
        #endregion

        private void preferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (FormPreferences pref = new FormPreferences())
                pref.ShowDialog(this);
        }
        #endregion

        private void tbCommandInput_KeyDown(object sender, KeyEventArgs e)
        {
            if ( e.KeyData == Keys.Enter )
            {
                ExecuteCommand(tbCommandInput.Text, true);
                tbCommandInput.Clear();
            }
        }
        private void ExecuteCommand(string cmd)
        {
            ExecuteCommand(cmd, false);
        }

        private void ExecuteCommand(string cmd, bool save)
        {
            if (cmd == string.Empty)
                return;
            try
            {
                /*Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
	            IPAddress[] addresses = Dns.GetHostAddresses(Properties.Settings.Default.UDPCommandReceiver);
                if (addresses.Length <= 0)
                    throw new Exception("Could not resolve \"" + Properties.Settings.Default.UDPCommandReceiver + "\".");*/
                ASCIIEncoding a = new ASCIIEncoding();
                //UdpClient c = new UdpClient();
                //c.Send(a.GetBytes(cmd), a.GetByteCount(cmd), new IPEndPoint(addresses[0], Properties.Settings.Default.UDPCommandPort));
                //s.SendTo(a.GetBytes(cmd), new IPEndPoint(addresses[0], Properties.Settings.Default.UDPCommandPort));
                m_CommandSocket.Send(a.GetBytes(cmd), a.GetByteCount(cmd));
            }
            catch (System.Exception ex)
            {
                printError("Could not transmit command: " + ex.Message);
            }
            if (cmd != string.Empty && save)
            {
                Properties.Settings.Default.RecentCommands.Remove(cmd);
                if (Properties.Settings.Default.RecentCommands.Count >= Properties.Settings.Default.RecentCommandsMax && Properties.Settings.Default.RecentCommands.Count > 0)
                    Properties.Settings.Default.RecentCommands.RemoveAt(Properties.Settings.Default.RecentCommands.Count - 1);
                Properties.Settings.Default.RecentCommands.Insert(0, cmd);
                Properties.Settings.Default.Save();
            }
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            if (keyData == Keys.Tab)
            {
                if (tbCommandInput.SelectedText != string.Empty)
                {
                    tbCommandInput.Select(tbCommandInput.TextLength - 1, 0);
                    tbCommandInput.AppendText(" ");
                }
                return true;
            }
            else
            {
                return base.ProcessDialogKey(keyData);
            }
        }

        private void ghostOutputText_TextChanged(object sender, EventArgs e)
        {
            if (!ghostOutputText.Focused && ghostOutputText.TextLength > 0)
            {
                if (IsRunningOnMono())
                {
                    ghostOutputText.Select(ghostOutputText.TextLength,0);
                    ghostOutputText.ScrollToCaret();
                    ghostOutputText.Invalidate();
                }
                else
                {
                    //scroll to bottom using windows api
                    PostMessage(ghostOutputText.Handle, WM_VSCROLL, new IntPtr(SB_BOTTOM), IntPtr.Zero);
                }
            }
        }

        private void configureTriggersToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (FormTriggers triggers = new FormTriggers(m_Triggers))
                triggers.ShowDialog(this);
        }

        private void timerCheckPort_Tick(object sender, EventArgs e)
        {
            if (IsGHostListeningLocally(Properties.Settings.Default.UDPCommandPort))
            {
                enableInput();
                timerCheckPort.Stop();
            }
            else
                disableInput();
        }

        private bool IsGHostListeningLocally(int port)
        {
            //TODO: this is not the best way to determine the port status, we should maybe use windows api for this
            try
            {
                Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                sock.Bind(new IPEndPoint(IPAddress.Loopback, port));
                sock.Close();
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                if (ex.ErrorCode == 10048)
                {
                    //port is in use
                    return true;
                }
            }
            //port is not in use (e.g. ghost does not listen to us)
            return false;
        }

        private void showHideWindowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Visible = !Visible;
            if (Visible)
                Activate();
        }

        private void tsCommandsDropDownButton_DropDownOpening(object sender, EventArgs e)
        {
            commandHistoryToolStripMenuItem.DropDownItems.Clear();
            foreach (string s in Properties.Settings.Default.RecentCommands)
            {
                commandHistoryToolStripMenuItem.DropDownItems.Add(s, Properties.Resources.console16, new EventHandler(cmdClicked));
            }
            if (commandHistoryToolStripMenuItem.HasDropDownItems)
            {
                commandHistoryToolStripMenuItem.Enabled = true;
                commandHistoryToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator());
                commandHistoryToolStripMenuItem.DropDownItems.Add(new ToolStripMenuItem("Clear history", Properties.Resources.historyclear16, new EventHandler(clearHistoryClicked)));
            }
            else
                commandHistoryToolStripMenuItem.Enabled = false;
        }

        private void cmdClicked(object sender, EventArgs e)
        {
            ToolStripMenuItem item = sender as ToolStripMenuItem;
            ExecuteCommand(item.Text, true);
        }

        private void clearHistoryClicked(object sender, EventArgs e)
        {
            Properties.Settings.Default.RecentCommands.Clear();
        }

        private void loginTimer_Tick(object sender, EventArgs e)
        {
            ExecuteCommand("rcon_password lol", false);
        }

        private void EnableAutoBroadcast( )
        {
            if (UPnPBroadcastEnabled)
                ExecuteCommand("rcon_autobroadcast " + Properties.Settings.Default.UPnPBroadcastPort.ToString(), false);
            else
            {
                PunchHole();
                ExecuteCommand("rcon_autobroadcast 6112 " + Properties.Settings.Default.LanGamePunchPort.ToString(), false);
                PunchTimer.Start();
            }
        }

        private void PunchHole()
        {
            try
            {
                Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                s.Bind(new IPEndPoint(IPAddress.Any, 6112));
                byte[] data = Encoding.ASCII.GetBytes("*punch*");
                IPAddress[] addresses = Dns.GetHostAddresses(Properties.Settings.Default.UDPCommandReceiver);
                s.SendTo(data, new IPEndPoint(addresses[0], Properties.Settings.Default.LanGamePunchPort));
                s.Shutdown(SocketShutdown.Both);
                s.Close();
                tslPuncher.Visible = true;
                disablePunchIconTimer.Start();
            }
            catch (System.Exception ex)
            {
                printError("Could not punch hole in firewall: " + ex.Message);
            }
        }

        private void PunchTimer_Tick(object sender, EventArgs e)
        {
            PunchHole();
            PunchTimer.Interval = Properties.Settings.Default.PunchInterval * 1000;
        }

        private void punchHoleInFirewallManuallyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            printLine("Punching hole into the firewall...");
            PunchHole();
        }

        private void disablePunchIconTimer_Tick(object sender, EventArgs e)
        {
            disablePunchIconTimer.Stop();
            tslPuncher.Visible = false;
        }

        private void statusLabel_MouseEnter(object sender, EventArgs e)
        {
           /* ToolStripStatusLabel label = sender as ToolStripStatusLabel;
            label.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
            foreach (ToolStripItem s in statusStrip1.Items)
            {
                if ((string)s.Tag == "statusIcon" && s != label)
                    s.DisplayStyle = ToolStripItemDisplayStyle.Image;
            }*/
        }

        private void statusLabel_MouseLeave(object sender, EventArgs e)
        {
            /*ToolStripStatusLabel label = sender as ToolStripStatusLabel;
            label.DisplayStyle = ToolStripItemDisplayStyle.Image;*/
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show(
                "anomus " + Application.ProductVersion + Environment.NewLine +
                "©2008-2009 Lucas Romero (GPL License)" + Environment.NewLine + Environment.NewLine +
                "GHost++" + Environment.NewLine +
                "©2008-2009 Trevor Hogan (Apache License)" + Environment.NewLine + Environment.NewLine + 
                "Also using Mono.Nat, a component of the Mono Framework");
        }

        private void tabControl1_Selected(object sender, TabControlEventArgs e)
        {
            if (e.TabPage == tabConsole)
                tbCommandInput.Select();
            else if (e.TabPage == tabCurrentGame)
            {
                tbCurrentChatInput.Select();
                rtbCurrentChatOutput.Select();
                ActionRefreshGameDetails();
                PlayerRefreshTimer.Start();
            }
        }

        private void EventGameHosted(string name, int port)
        {
            if (GameStatus != EGameStatus.Lobby)
            {
                GameStatus = EGameStatus.Lobby;
                rtbCurrentChatOutput.Enabled = true;
                tbCurrentChatInput.Enabled = true;
                lwCurrentPlayers.Enabled = true;
                rtbCurrentChatOutput.Clear();
                tabCurrentGame.Text = name;
                tslGameInLobby.Visible = true;
            }
            
            if (tabControl1.SelectedTab == tabCurrentGame && !PlayerRefreshTimer.Enabled)
            {
                ActionRefreshGameDetails();
                PlayerRefreshTimer.Start();
            }
            
            if (port > 0)
                foreach (INatDevice n in m_NatDevices)
                {
                    Mapping m = new Mapping(Protocol.Tcp, port, port);
                    printGUILine("Creating UPnP port mapping for game on port " + port);
                    try
                    {
                    	n.BeginCreatePortMap(m, new AsyncCallback(EventGamePortMapping), new GamePortMappingData(m, n));
                    }
                    catch (System.Exception ex)
                    {
                        printError("ERROR while begin creating UPnP port mapping for game: " + ex.Message);
#if DEBUG
                        printError(ex.StackTrace);
#endif
                    }
                }
        }

        private class GamePortMappingData 
        {
            public GamePortMappingData(Mapping mapping, INatDevice device)
            {
                Mapping = mapping;
                Device = device;
            }
            public Mapping Mapping;
            public INatDevice Device;
        };

        private void EventGamePortMapping(IAsyncResult result)
        {
            GamePortMappingData data = result.AsyncState as GamePortMappingData;
            try
            {
            	data.Device.EndCreatePortMap(result);
                printGUILine("Mapped UPnP port: " + data.Mapping.ToString());
                if (data.Mapping.PublicPort == Properties.Settings.Default.UPnPBroadcastPort)
                {
                    UPnPBroadcastEnabled = true;
                    PunchTimer.Stop();
                    EnableAutoBroadcast();
                }
            }
            catch (System.Exception ex)
            {
                printError("ERROR while creating UPnP port mapping for game: " + ex.Message);
#if DEBUG
                printError(ex.StackTrace);
#endif
            }
        }

        void GetPortMappingCallback(IAsyncResult result)
        {
            GamePortMappingData data = (GamePortMappingData)result.AsyncState;
            try
            {
                Mapping m = data.Device.EndGetSpecificMapping(result);
                printGUILine("Got UPnP mapping: " + m.ToString());
                if (m.PublicPort != Properties.Settings.Default.UPnPBroadcastPort || m.PrivatePort != 6112)
                {
                    Mapping newMapping = new Mapping(Protocol.Udp, 6112, Properties.Settings.Default.UPnPBroadcastPort);
                    data.Device.BeginCreatePortMap(newMapping, new AsyncCallback(EventGamePortMapping), new GamePortMappingData(newMapping, data.Device));
                }
                else
                {
                    UPnPBroadcastEnabled = true;
                    PunchTimer.Stop();
                    UPnPTimer.Stop();
                    EnableAutoBroadcast();
                }
            }
            catch (System.Exception ex)
            {
                printError("ERROR while creating UPnP port mapping for game: " + ex.Message);
#if DEBUG
                printError(ex.StackTrace);
#endif
            }
        }

        private void EventGameUnhosted(string name)
        {
            GameStatus = EGameStatus.None;
            rtbCurrentChatOutput.Enabled = false;
            tbCurrentChatInput.Enabled = false;
            lwCurrentPlayers.Enabled = false;
            tabCurrentGame.Text = "Current game";
            tslGameInLobby.Visible = false;
        }

        private void EventGameDetails(string details)
        {
            if (details == "no game in lobby")
            {
                //printLine("NO GAME!!!!!");
                EventGameUnhosted(string.Empty);
            }
            else
            {
                if (GameStatus != EGameStatus.Lobby)
                    EventGameHosted("Current game", -1);
                int index = 0;
                string[] players = details.Split('\n');
                lwCurrentPlayers.BeginUpdate();
                if (players.Length != lwCurrentPlayers.Items.Count)
                    lwCurrentPlayers.Items.Clear();
                foreach (string player in players)
                {
                    string[] args = player.Split('\t');
                    UpdatePlayer(index, args[0], args[2], args[3], args[1], int.Parse(args[4]));
                    index++;
                }
                lwCurrentPlayers.EndUpdate();
            }
        }

        private void PlayerRefreshTimer_Tick(object sender, EventArgs e)
        {
            if (GameStatus == EGameStatus.Lobby)
                ActionRefreshGameDetails();
            else
                PlayerRefreshTimer.Stop();
        }

        private void ActionRefreshGameDetails()
        {
            ExecuteCommand("rcon_gamedetails", false);
        }

        private void tabControl1_Deselected(object sender, TabControlEventArgs e)
        {
            if (e.TabPage == tabCurrentGame)
            {
                PlayerRefreshTimer.Stop();
            }
        }

        private void statusLabel_Click(object sender, EventArgs e)
        {
            ToolStripStatusLabel label = sender as ToolStripStatusLabel;
            if (label.DisplayStyle == ToolStripItemDisplayStyle.Image)
                label.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
            else
                label.DisplayStyle = ToolStripItemDisplayStyle.Image;
        }

        private void tbCurrentChatInput_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter)
            {
                ActionSendLobbyChat(tbCurrentChatInput.Text);
                tbCurrentChatInput.Clear();
            }
        }

        private void ActionKickPlayer(string name)
        {
            ExecuteCommand("rcon_kick " + name);
        }

        private void ActionSendLobbyChat(string message)
        {
            if (message == string.Empty)
                return;
            ExecuteCommand("rcon_sendlobbychat " + message);
        }

        private void ActionCloseSlot(int index)
        {
            ExecuteCommand("close " + Convert.ToString(index + 1));
        }

        private void ActionOpenSlot(int index)
        {
            ExecuteCommand("open " + Convert.ToString(index + 1));
        }

        private void ActionOpenAllSlots()
        {
            ExecuteCommand("openall");
        }

        private void ActionHostGame(string gamename, bool pubgame)
        {
            if (pubgame)
                ExecuteCommand("pub " + gamename);
            else
                ExecuteCommand("priv " + gamename);
        }

        private void ActionUnhostGame()
        {
            ExecuteCommand("unhost");
        }

        private void ActionSwapPlayers(int player1, int player2)
        {
            ExecuteCommand(String.Format("swap {0} {1}", player1+1, player2+1));
        }

        private void closeSlotToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (ListViewItem i in lwCurrentPlayers.SelectedItems)
            {
                ActionCloseSlot(i.Index);
            }
        }

        private void openSlotsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (ListViewItem i in lwCurrentPlayers.SelectedItems)
            {
                ActionOpenSlot(i.Index);
            }
        }

        private void openAllSlotsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ActionOpenAllSlots();
        }

        private void kickPlayersToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (ListViewItem i in lwCurrentPlayers.SelectedItems)
            {
                ActionKickPlayer(i.Text);
            }
        }

        private void unhostGameToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("Are you sure you want to unhost the current game?", "Are you sure?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
                ActionUnhostGame();
        }

        private void clearConsoleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ghostOutputText.Clear();
        }

        private void hostGameToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (FormCreateGame f = new FormCreateGame())
            {
                if (f.ShowDialog() == DialogResult.OK)
                {
                    CreateGameData data = f.GameData;
                    ActionHostGame(data.Name, data.Public);
                    if (data.AutoCloseSlot)
                        ActionCloseSlot(0);
                    Properties.Settings.Default.RecentGamenames.Remove(data.Name);
                    Properties.Settings.Default.RecentGamenames.Add(data.Name);
                }
            }
        }

        private void unhostGameToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            ActionUnhostGame();
        }

        private void swapPlayersToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lwCurrentPlayers.SelectedItems.Count != 2)
            {
                MessageBox.Show("You have to select exactly 2 players in order to swap them!");
                return;
            }
            ActionSwapPlayers(lwCurrentPlayers.SelectedItems[0].Index, lwCurrentPlayers.SelectedItems[1].Index);
        }

        private void UPnPTimer_Tick(object sender, EventArgs e)
        {
            if (GHostType != EGHostType.Remote)
            {
                UPnPTimer.Stop();
                return;
            }
            Mapping m = new Mapping(Protocol.Udp, 6112, Properties.Settings.Default.UPnPBroadcastPort);
            foreach (INatDevice device in m_NatDevices)
            {
                try
                {
                    printLine("Checking UPnP portmapping...");
                    printLine(m.ToString());
                    device.BeginGetSpecificMapping(Protocol.Udp, Properties.Settings.Default.UPnPBroadcastPort, new AsyncCallback(GetPortMappingCallback), new GamePortMappingData(m, device));
                }
                catch (System.Exception ex)
                {
            	    printError("Could not map broadcasting port: " + ex.Message);
#if DEBUG
                    printError(ex.StackTrace);
#endif
                }
            }
        }
    }
}